package clientcore

import (
	"context"
	"net"
	"strings"
	"sync"
	"time"

	"gitee.com/huanghua_2017/blade/comm"
	"gitee.com/huanghua_2017/blade/state"
	"gitee.com/huanghua_2017/blade/types"
	"golang.org/x/xerrors"
)

func (slf *Core) stopSlave(laddr string) error {
	wsc := slf.state.FindSlaveWSConn(laddr)
	if wsc == nil {
		return xerrors.New("no corresponding connection was found")
	}

	wsc.Dead = true
	wsc.Wschan <- []byte{'1'}
	close(wsc.Stoping)
	close(wsc.Notify)
	slf.state.RemoveSlaveWSConn(laddr)
	slf.logger.Printf("slave 停止端口映射 %s\n", laddr)
	return nil
}

func (slf *Core) startSlave(laddr, raddr string) error {
	wsc := slf.state.FindSlaveWSConn(laddr)
	if wsc != nil {
		return xerrors.New("the same port cannot be mapped multiple times")
	}
	wconn, res, err := slf.connServer(context.Background(), types.HelloReqStruct{Proto: types.SLAVE, Address: raddr})
	slf.logger.Printf("slave 连接 raddr： %s, err: %v\n", raddr, err)
	if err != nil {
		return err
	}
	sc := &state.SlaveConn{
		Vaild:   true,
		Dead:    false,
		Conn:    wconn,
		Laddr:   laddr,
		Raddr:   raddr,
		Resaddr: res.Address,
		Stoping: make(chan struct{}, 1),
		Wschan:  make(chan []byte, 1000),
		Notify:  make(chan struct{}, 5),
		Begin:   time.Now(),
	}

	slf.state.UpdateSlaveWSConn(sc.Laddr, sc)
	slf.startSlaveListen(sc)

	return nil
}

func (slf *Core) startSlaveListen(sc *state.SlaveConn) {
	wsaddr := sc.Conn.RemoteAddr().String()
	var once sync.Once
	exitfun := func() {
		if sc.Dead {
			return
		}
		sc.Vaild = false
		close(sc.Stoping)
		slf.state.RemoveSlaveWSConn(sc.Laddr)

		go func() {
			for trycnt := 0; !sc.Dead; trycnt++ {
				wconn, res, err := slf.connServer(context.Background(), types.HelloReqStruct{Proto: types.SLAVE, Address: sc.Raddr})
				slf.logger.Printf("slave %d/NaN 重新连接 raddr： %s, err: %v\n", trycnt+1, sc.Raddr, err)
				if err != nil {
					time.Sleep(time.Second * 5)
					continue
				}
				sc.Conn = wconn
				sc.Vaild = true
				sc.Dead = false
				sc.Resaddr = res.Address
				sc.Stoping = make(chan struct{}, 1)
				slf.state.UpdateSlaveWSConn(sc.Laddr, sc)
				slf.startSlaveListen(sc)
				return
			}
		}()
	}

	go slf.slaveDataExchange(sc)

	go func() {
		tc := time.NewTicker(time.Second * 3).C
		for {
			for {
				select {
				case <-sc.Stoping:
					slf.logger.Printf("slave %s -> %s 连接已断开, 退出数据发送协程", sc.Laddr, wsaddr)
					return
				case <-tc:
					comm.WriteWSMessage(sc.Conn, []byte{'0'})
				case wmsg := <-sc.Wschan:
					err := comm.WriteWSMessage(sc.Conn, wmsg)
					if err != nil {
						slf.logger.Printf("slave 发送数据失败: %s, %+v\n", wsaddr, err)
						once.Do(exitfun)
						return
					}
				}
			}
		}
	}()

	go func() {
		for {
			select {
			case <-sc.Stoping:
				slf.logger.Printf("slave 管道被关闭,关闭上游连接: %s\n", wsaddr)
				sc.Conn.Close()
				return
			default:
				sc.Conn.SetReadDeadline(time.Now().Add(time.Second * 5))
				_, msg, err := sc.Conn.ReadMessage()
				if err != nil {
					slf.logger.Printf("slave 上游服务器断开连接: %s, %+v\n", wsaddr, err)
					once.Do(exitfun)
					return
				}

				l := len(msg)
				if l > 0 {
					msg = comm.DecodeMessage(msg)
					if msg[0] != '0' {
						sc.Notify <- struct{}{}
					}
				}
			}
		}
	}()
}

func (slf *Core) slaveDataExchange(sc *state.SlaveConn) {
	sc.Begin = time.Now()
	slf.state.InsertSlaveConn(sc.Laddr, sc)
	defer slf.state.DeleteSlaveConn(sc.Laddr)

	for {
		select {
		case <-sc.Stoping:
			return
		case _, ok := <-sc.Notify:
			if !ok {
				slf.logger.Printf("slave 退出数据交换协程")
				return
			}

			lconn, err := net.Dial("tcp", sc.Laddr)
			slf.logger.Printf("slave 连接本地地址 %s : %v\n", sc.Laddr, err)
			if err != nil {
				continue
			}

			ustr := strings.Split(slf.Cfg.ServerAddrs[slf.Cfg.Index], ":")[0] + ":" + sc.Resaddr
			rconn, err := net.Dial("tcp", ustr)
			slf.logger.Printf("slave 连接服务器地址 %s : %v\n", ustr, err)
			if err != nil {
				lconn.Close()
				continue
			}

			go func() {
				slf.logger.Printf("slave %s -> %s 开始转发数据\n", rconn.RemoteAddr().String(), lconn.RemoteAddr().String())
				comm.TCPConnCopy(slf.logger, lconn, rconn)
				slf.logger.Println("slave 转发数据关闭")
			}()
		}
	}
}
